ARM 存储器映射和 CMSIS

ARM 存储器映射

ARM Cortex-M 内核规定了一套统一的地址空间布局。对于 ARM CPU,其所有的内容都是地址。包括 Flash、RAM 和其他外设,都会对应到某一段地址空间。CPU 并不知道哪个地址是什么,只知道向某个地址写入或者读取。至于地址和设备的对应关系,就是 ARM 存储器映射。

对于 32 位的 Cortex-M,其地址宽度为 32 位。也就是说,CPU 最大可以访问的地址有 2322^{32} 个,共 4GB,即从 0x000000000xffffffff 均可以访问。这个范围被叫做地址空间。这个空间中的不同区域被分配给不同硬件。

下图展示了 ARM 架构中系统内存映射的核心区域划分:

特别的,对于 Cortex-M 系列,其有:

地址范围 区域名称 描述
0x0000_0000~0x1FFF_FFFF 代码区域(Code) 用于存放程序代码,通常映射到芯片内部的 Flash 存储器。
0x2000_0000~0x3FFF_FFFF SRAM 区域 用于存放数据(变量、堆栈等),通常映射到芯片内部的 SRAM
0x4000_0000~0x5FFF_FFFF 片上外设 映射到芯片厂商集成的外设,如 GPIO 等
0x6000_0000~0x9FFF_FFFF 外部 RAM 用于拓展外部存储器(SDRAM,SRAM 等)
0xA000_0000~0xDFFF_FFFF 外部设备 用于拓展外部设备(FPGA 等)
0xE000_0000~0xE00F_FFFF 私有外设总线(PPB) 这部分包含了 Cortex-M 本身的外设:
- NVIC (嵌套向量中断控制器)
- SCB(系统控制块)
- SysTick(系统定时器)
- MPU(内存保护单元)

特别的,对于 STM32,代码实际上存储在 0x0800_0000 位置。上表是 ARM 官方的规定,实际代码的存放位置是由厂商决定的。通过芯片厂商 ST 的硬件实现,0x0800_0000 位置的代码被重映射到 0x00_0000 上。

很多 MCU 都具有启动引脚(BOOT 0/1)。这些引脚在上电复位时的电平,决定了芯片将什么物理内存映射到启动地址 0x0000_0000。对于 STM32,其支持从主 Flash 启动(重定向到 0x0800_0000)、从程序存储器 BootROM 启动(ISP 模式,串口/USB 下载程序在 0x1FFF_0000)、从内置 SRAM 启动(0x2000_0000)等。

在 C 语言中一般用以下方式设置外设、读写内存:

*(volatile uint32_t*)0x40021018 = 0x1;

这本质是在向 0x40021018 发起一次总线写操作。如果这个地址是在 Flash 中,则是向 Flash 中写内容;如果是某个外设地址,就是在写寄存器。通过这种方式,ARM 实现了各种 CPU 操作的指令统一。

CMSIS

对于外设、内存的读写和设置都是通过对某个地址进行读写,这种方式虽然统一了硬件访问模型,但是通过裸地址来访问会存在一系列问题:

  • 不同厂商命名风格不同
  • 寄存器地址可读性差,难以维护
  • 不同编译器和 SDK 之间缺乏统一接口

例如:

*(volatile uint32_t*)(0x40021000 + 0x18) |= (1 << 5);

很难能够看出是在操作哪个外设的什么寄存器。

因此,ARM 提出了 CMSIS(Cortex Microcontroller Software Interface Standard)规范,用于统一 Cortex-M 的软件接口。

CMSIS 本身并不负责设计硬件地址映射,而是在已有的 Memory Map 的基础上提供统一的软件描述方式。下图为其软件架构。

+-------------------------------------------------+  
| 用户应用代码 | 中间件 (RTOS, 文件系统, UI, 网络)     |  
+-------------------------------------------------+  
|                    CMSIS 层                     |  
| +-----------+ +------------+ +------+ +-------+ |  
| |   CMSIS   | |   CMSIS    | | CMSIS| | CMSIS | |  
| |   Driver  | |    DSP     | |  RTOS| |  NN   | |  
| +-----------+ +------------+ +------+ +-------+ |  
| |               CMSIS-Core                      |  
| +-----------------------------------------------+  
+-------------------------------------------------+  
|             硬件 (ARM Cortex-M 核 + 外设)         |  
+-------------------------------------------------+

最底层为硬件,包括 Cortex-M 内核和芯片厂商设计的外设。中间层为 CMSIS,他与内核和底层硬件交互。最上层为用户应用和第三方中间件,他们通过调用 CMSIS 的 API 访问硬件,从而和具体的芯片型号解耦。

CMSIS 包含很多部分:

  • CMSIS Core,提供与 Cortex-M0/3/4、SCx00 处理器与外围寄存器之间的接口;
  • CMSIS DSP,包含以定点和单精度浮点实现的多种函数的 DSP 库;
  • CMSIS RTOS,用于线程控制、资源和时间管理的 RTOS 的标准化编程接口;
  • CMSIS SVD,用于包含完整微处理器系统的程序员视图的系统视图描述 XML 文件;
  • CMSIS NN,是一组高校的神经网络(NPU)内核;
  • CMSIS Driver 接口适用于很多 MCU 系列;
  • CMSIS DAP,是 Cortex 调试访问端口(DAP)的标准化接口;
  • 等等。

现仅对 CMSIS-Core 做介绍。

CMSIS-Core(Cortex-M)组件为 Arm Cortex-M 系列芯片提供了基本的运行时系统功能,同时让用户能够访问处理器核心及各种外设。具体来说,它定义了:

  • 针对 Cortex-M 处理器的硬件抽象层(HAL)为 SysTick、NVIC、系统控制块寄存器、MPU 寄存器、FPU 寄存器以及核心访问功能提供了标准化的定义。
  • 系统异常名称,用于在处理系统异常时避免兼容性问题。
  • 组织头文件的方法,包括针对各种设备特有的中断所制定的命名规则。
  • 各 MCU 制造商所采用的系统初始化方法。例如,标准化的 SystemInit() 函数对于配置设备的时钟系统至关重要。
  • 用于生成那些标准 C 函数所不支持的各种 CPU 指令的内置函数。
  • 一个用于确定系统时钟频率的变量,该变量有助于简化 SysTick 定时器的配置过程。

对于每一个例程,其包含了以下内容:

  • 启动文件 startup_<device>.s,即启动文件。参考 Cortex-M 启动文件
  • 系统配置文件 system<device>.csystem_<device>.h,如 system_stm32f4xx.c。其中存储了设备的各种配置信息。
  • 设备头文件 <device>.h,提供了对处理器核心和所有外设的访问接口,如 stm32f411xe.h

在程序复位时,其会执行启动文件,该文件会在 Reset_Handler 中调用 System_Init()。系统配置文件负责处理器的时钟设置。

设备头文件中提供了对以下特定设备相关的功能的访问接口:

  • 外设接口。其为外设提供了标准化的寄存器布局。
  • NVIC 接口。提供了标准符号和函数。
  • CPU 指令内置函数。用于调用 CPU 的特殊指令,比如睡眠模式或 NOP。
  • Systick Timer(SYSTICK),用于配置和启动周期性定时器中断。

HAL 库

在 CMSIS 的基础上,芯片厂商通常还会提供 HAL 库。这些库通过调用 CMSIS 接口来提供更加易于使用的驱动 API。

如,对于 GPIO 的操作,如果不使用 CMSIS/HAL 库,其需要直接操作寄存器:

*(volatile uint32_t*)0x48000014 |= (1 << 5);

利用 CMSIS,则可以写成:

GPIOA->ODR |= (1 << 5);

可以看到,其提供了 GPIOA、GPIO_TypeDef、__IO 等。但是依然在直接操作寄存器 ODR

如果利用 ST 公司提供的 HAL 库,则可以写成:

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

可以看到其提供了更好的封装。

Last modified: 2026-05-24